<?php
define('DS', DIRECTORY_SEPARATOR);
define('SP', " ");//space
define('NL', "\n");//next line
/**
 * Description of animation
 *
 * @author jguan
 */
class Animation{
    public $outputPath;//the result path
    public $imPath;//ImageMagic 's root path
//
    public $debug = true;
    public $src = array();
    public $bg = array("bg.png", "bg2.png");//array of backgound 's backgrounds
    public $fg = array("fg.png");//array of backgound 's forgrounds
    public $logo = "logo.png";
    public $sidebar = "sidebar.png";//level demonstrate
    public $titleLeft = "CALIBEXT NORD";
    public $titleCenter = "Total";
    public $aniName = "animation";//output name
    public $annotation = array();//text annotation on background
//
    private $fps = 3;//3 frames per second
    private $delay = 20;//every frame have 20 ms delay
    private $xSize = 597;
    private $ySize = 532;
    private $errors = array();//array to store errors;
    private $background;
    private $tmpImgPath;//the path of tmp gifs generated
    function __construct($imPath, $outputPath=""){

        $this->imPath = $imPath;
        $this->outputPath = $outputPath ? $outputPath : dirname(__FILE__);
        if($this->checkPath()){
            $this->init();
        }else{
            $this->printErrors();
        }
    }
    function init(){
        $this->convert = $this->imPath.DS."convert";
        $this->composite = $this->imPath.DS."composite";
        $this->montage = $this->imPath.DS."montage";
        $this->ffmpeg = $this->imPath.DS."ffmpeg";

        $this->tmpImgPath = $this->outputPath.DS."imagetmp";
        @mkdir($this->tmpImgPath);

//$this->srcDirIter = new DirectoryIterator($this->pngPath);
    }
    function makeAniGifWmv(){
        $this->makeAnimation("both");
    }
    function makeWmv(){
        $this->makeAnimation("wmv");
    }
    function makeAniGif(){
        $this->makeAnimation("gif");
    }
    function makeAnimation($type){
        $typeMap = array("gif"=>0, "wmv"=>1, "both"=>2);
        if($this->checkFiles()){
            $this->generateTmpImgs();
            switch($typeMap[$type]){
                case 0:
                    $this->generateAniGif();
                    break;
                case 1:
                    $this->generateWmv();
                    break;
                case 2:
                    $this->generateAniGif();
                    $this->generateWmv();
                    break;
                default:
                    $this->generateAniGif();
                    break;
            }
        }else{
            $this->printErrors();
        }
    }
    /**
     * pop bubble sort 2-d array by some one item
     */
    function sortByItem(&$array, $sidx = 0){//sort index
        if($array){
            for($len1 = count($array), $idx1 = 0; $idx1 < $len1; $idx1++){
                $tmpIdx = $idx1;
                for($idx2 = $idx1; $idx2 < $len1; $idx2++){
                    if($array[$tmpIdx][$sidx] > $array[$idx2][$sidx]){
                        $tmpIdx = $idx2;
                    }
                }
                if($tmpIdx != $idx1){
                    $tmp = $array[$idx1];
                    $array[$idx1] = $array[$tmpIdx];
                    $array[$tmpIdx] = $tmp;
                }
            }
        }
    }
    function generateTmpImgs(){
        $this->sortByItem($this->src, 1);
        $this->sortByItem($this->bg, 0);
        $this->sortByItem($this->fg, 0);
        $this->generateBg();

        $fileIndex = 1;
        foreach($this->src as $srcFile){
            $filename = $srcFile[0];
            $basename = array_pop(explode(DS, $filename));
            $filedate = $srcFile[1];
            $ext = array_pop(explode(".", $filename));
            if($ext == "png"){
//fix for aniGif gener wrong order.
                if($fileIndex < 10){
                    $prefix = "000";
                }elseif($fileIndex < 100){
                    $prefix = "00";
                }elseif($fileIndex < 1000){
                    $prefix = "0";
                }
                $tmpNameBase = $this->tmpImgPath.DS."tmpfile_$prefix".$fileIndex++;
                $tmpName = $tmpNameBase.".png";
                echo "converting $basename to ".NL;
//transpancy and resize
                $transpancy = '-alpha set -transparent "#000000"';
                $resize = "-filter point -resize 512x512";
                $cmd = $this->convert.SP.$filename.SP.$transpancy.SP.$resize.SP.$tmpName;
                $this->runCmd($cmd);
                //composite data and background
                $composite = '-compose dissolve -set "option:compose:args" 70,100 -geometry +1+19';
                $cmd = $this->convert.SP.$this->background.SP.$tmpName.SP.$composite.SP."-composite".SP.$tmpName;
                $this->runCmd($cmd);
                //adding foreground
                for($i = 0, $len = count($this->fg); $i < $len; $i++){
                    $cmd = $this->convert.SP.$tmpName.SP.$this->fg[$i][0].SP.$composite.SP."-composite".SP.$tmpName;
                    $this->runCmd($cmd);
                }
                //add text
                $this->annotation['date']['content'] = $filedate;
                $this->annotation['name']['content'] = substr($basename, 0, -4);
                $date = '-gravity northeast -annotate +0+5 "'.$filedate.'"';
                $name = '-gravity southwest -annotate +0+0 "'.substr($basename, 0, -4).'"';
                $annotations =
                    $cmd = $this->convert.SP.$tmpName.SP.$name.SP.$date.SP.$tmpName;
                $this->runCmd($cmd);
            }
        }
    }
    /**
     * array with structure['gravity','xOffset','yOffset','content'] to imagemagick string
     * if gravity is not set, xOffset and yOffset will calculate by image (0,0)point
     * @param <array> $gravity
     */
    function annotation($gravity){
        if($gravity['gravity']){
            $geometry = "-gravity {$gravity['gravity']} {$gravity['xOffset']}{$gravity['yOffset']}";
        }else{
            $geometry = "-geometry {$gravity['xOffset']}{$gravity['yOffset']}";
        }
        return $geometry.SP.'-annotate "'.$gravity['content'].'"'.SP;
    }
    function generateAniGif(){
        $delay = "-layers optimize -delay $this->delay";
        $loop = "-loop 0";
        $gifs = $this->tmpImgPath.DS."*.png";
        $agif = $this->outputPath.DS."$this->aniName.gif";
        $resize = "-resize".SP.$this->xSize."x".$this->ySize;
        $cmd = $this->convert.SP.$delay.SP.$loop.SP.$gifs.SP.$resize.SP.$agif;
        $this->runCmd($cmd);
    }
    function generateWmv(){
        $fps = "-r $this->fps";
        $input = "-i $this->tmpImgPath".DS."tmpfile_%4d.png";
        $output = $this->outputPath.DS."$this->aniName.wmv";
        $size = "-s".SP.$this->xSize."x".$this->ySize;
        $cmd = $this->ffmpeg.SP.$fps.SP.$size.SP.$input.SP.$output;
        $this->runCmd($cmd);
    }
    function generateBg(){
        $this->background = $this->tmpImgPath.DS."background.tmp.gif";
//using imagemagic 's convert, composite and montage to create background
//1.composite bgs
        $bgLength = count($this->bg);
        if($bgLength > 0){
            copy($this->bg[0][0], $this->background);
            if($bgLength > 1){
                for($i = 1; $i < $bgLength; $i++){
                    $dissolve = "-dissolve 100x".$this->bg[$i][1];
                    $cmd = $this->composite.SP.$dissolve.SP.$this->background.SP.$this->bg[$i][0].SP.$this->background;
                    $this->runCmd($cmd);
                }
            }
        }

//2.adding sidebar with logo
        $cmd = $this->montage.SP.$this->background.SP.$this->sidebar.SP."-mode concatenate -gravity center $this->background";
        $this->runCmd($cmd);
        $dissolve = "-dissolve 100";
        $cmd = $this->composite.SP."-gravity southeast".SP.$this->logo.SP.$this->background.SP.$this->background;
        $this->runCmd($cmd);
//3.adding title bar
        $titleBg = '-background "#707070" -splice 0x20';
        $titleLeft = '-gravity northwest -annotate +2+5 "'.$this->titleLeft.'"';//
        $titleCenter = '-gravity north -annotate +0+5 "'.$this->titleCenter.'"';
        $cmd = $this->convert.SP.$this->background.SP.$titleBg.SP.$titleCenter.SP.$titleLeft.SP.$this->background;
        $this->runCmd($cmd);
    }
    /**
     * read file list from prepared listfile to array
     * the array structure is array(array(filepath,parameter));
     * @param <string> $listFilePath
     * @param <bool> $firstHeader
     * @return <array>
     */
    function readListFile($listFilePath, $firstHeader=FALSE){
        $list = array();
        if(is_file($listFilePath)){
            $handle = fopen($listFilePath, "r");
            $row = 1;
            while($item = fgetcsv($handle)){
                if($firstHeader && $row++== 1){
                    continue;
                }
                $filepath = trim($item[0]);
                //using "#" begin for comment out it;
                if(substr($filepath, $start = 0, $length = 1) != "#"){
                    if(!file_exists($filepath)){
                        $this->errors["checkfiles"][] = "file".SP.$filepath.SP."doesnot exist.";
                    }else{
                        $list[] = $item;
                    }
                }
            }
            fclose($handle);
        }
        return $list;
    }
    //check folders exists and the right to be writable;
    function checkPath(){
        if(!file_exists($this->outputPath)){
            $this->errors[5] = "output folder does not exists.";
        }elseif(!is_writeable($this->outputPath)){
            $this->errors[6] = "output folder ".$this->outputPath." is not writeable.";
        }

        if(!file_exists($this->imPath)){
            $this->errors[7] = $this->imPath." does not exists.";
        }
        if($this->errors){
            return false;
        }
        return true;
    }
    /**
     * put exif infomation to generated animation gif
     */
    function setExif(){
        $agif = $this->outputPath.DS."$this->aniName.gif";
    }
    function checkFiles(){
        return!count($this->errors["checkfiles"]);
    }
    function printErrors(){
        foreach($this->errors as $error){
            print_r($error);
            echo NL;
        }
    }
    /**
     *  adding static text to background
     * @param <int> $x
     * @param <int> $y
     * @param <string> $text
     * @param <string> $name
     */
    function addText($x, $y, $text, $name){
        $newAnnotate = array($x, $y, $text);
        if($name){
            $this->annotates[$name] = $newAnnotate;
        }else{
            $this->annotates[] = $newAnnotate;
        }
    }
    function clearTmps(){
        $tmpDir = new DirectoryIterator($this->tmpImgPath);
        foreach($tmpDir as $tmpChild){
            if($tmpChild->isFile()){
                @unlink($this->tmpImgPath.DS.$tmpChild->getFilename());
            }
        }
        @rmdir($this->tmpImgPath);
    }
    function setFps($fps){
        $this->fps = $fps;
        $this->delay = round(100 / $fps);
    }
    function setSize($x = 0, $y = 0){
        if($x && ! $y){
            $y = round($x * $this->ySize / $this->xSize);
        }elseif($y && ! $x){
            $x = round($y * $this->xSize / $this->ySize);
        }elseif(!$x && ! $y){
            return;
        }
        $this->xSize = $x;
        $this->ySize = $y;
    }
    function runCmd($cmd){
        if($this->debug){
            echo $cmd.NL;
        }
        exec($cmd);
    }
    function __destruct(){
        $this->clearTmps();
    }
}
